package top.linyuxb.lycloud.rpc.starter.aop;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.linyuxb.lycloud.common.constant.CommunicationTypeEnum;
import top.linyuxb.lycloud.common.constant.LyRpcRobinType;
import top.linyuxb.lycloud.common.dto.LyRpcServerDto;
import top.linyuxb.lycloud.common.properties.LyGatewayProperties;
import top.linyuxb.lycloud.common.utils.LyRpcRibbonUtil;
import top.linyuxb.lycloud.rpc.starter.annotation.LyRpcMethod;
import top.linyuxb.lycloud.rpc.starter.annotation.LyRpcServer;
import top.linyuxb.lycloud.rpc.starter.common.FileUtils;
import top.linyuxb.lycloud.rpc.starter.common.LyRpcBeanUtil;
import top.linyuxb.lycloud.rpc.starter.common.LyRpcData;
import top.linyuxb.lycloud.rpc.starter.dto.LyRpcReferenceDto;
import top.linyuxb.lycloud.rpc.starter.dto.LyRpcRibbonDto;
import top.linyuxb.lycloud.rpc.starter.properties.LyRpcClientProperties;

import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Created by Intellij IDEA.
 *
 * @Author: linyuxb
 * @Date: 2024/5/31 15:44
 * @Desc:
 */
@Slf4j
public class MyInvocationHandlerRpc implements InvocationHandler {
    private final Class<?> classz;
    private final LyRpcServer lyRpcServer;
    private final LyRpcClientProperties lyRpcClientProperties;
    private static final Map<String, Object> beanMap = MapUtil.newHashMap();

    public MyInvocationHandlerRpc(Class<?> classz, LyRpcServer lyRpcServer, LyRpcClientProperties lyRpcClientProperties) {
        this.classz = classz;
        this.lyRpcServer = lyRpcServer;
        this.lyRpcClientProperties = lyRpcClientProperties;
    }

    /**
     * proxy 代理对象 method拦截方法 args方法上的参数值
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("MyInvocationHandlerRpc invoke");
        LyRpcMethod lyRpcMethod = method.getDeclaredAnnotation(LyRpcMethod.class);
        if (Objects.nonNull(lyRpcMethod)) {
            return this.doInvoke(lyRpcMethod, method, args);
        }
        return null;
    }

    /**
     * 执行插入
     *
     * @param lyRpcMethod
     * @param method
     * @param args
     * @return
     */
    private Object doInvoke(LyRpcMethod lyRpcMethod, Method method, Object[] args) {
        // 请求超时，优先级最低
        int timeOut = lyRpcServer.timeOut();
        if (lyRpcMethod.timeOut() > 0) {
            // 请求超时，优先级中
            timeOut = lyRpcMethod.timeOut();
        }
        // 1、获取ip地址 默认：127.0.0.1
        String ipAddress = "127.0.0.1";
        ServletRequestAttributes servletRequestAttributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.nonNull(servletRequestAttributes)) {
            HttpServletRequest request = servletRequestAttributes.getRequest();
            ipAddress = request.getRemoteAddr();
        }
        // 2、获取服务配置
        int retry = LyRpcRibbonUtil.initServer(lyRpcClientProperties.getType(), lyRpcClientProperties.getServers());
        // 3、获取负载均衡器
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        String className = stackTraceElements[4].getClassName();
        String[] split = className.split("\\.");
        String simpleName = split[split.length - 1];
        String methodName = stackTraceElements[4].getMethodName();
        if (!beanMap.containsKey(className)) {
            try {
                Class<?> aClass = Class.forName(className);
                Object bean = SpringUtil.getBean(aClass);
                if (Objects.nonNull(bean)) {
                    beanMap.put(className, bean);
                }
            } catch (Exception e) {
                log.error("doInvoke Class.forName error", e);
            }
        }
        Object bean = beanMap.get(className);
        String key1 = simpleName + "_" + methodName;
        LyRpcRibbonDto ribbon = LyRpcData.getRibbon(key1);
        LyRpcRobinType lyRpcRobinType = LyRpcRobinType.ROUND_ROBIN;
        if (Objects.nonNull(ribbon)) {
            lyRpcRobinType = ribbon.getRibbonType();
            // 请求超时最大优先级
            if (ribbon.getTimeOut() > 0) {
                timeOut = ribbon.getTimeOut();
            }
            if (ribbon.getRetry() > 0) {
                retry = ribbon.getRetry();
            }
        }

        // 支持指定重试次数
        return getObject(method, args, retry, lyRpcRobinType, ipAddress,
                simpleName, timeOut, bean, ribbon.getFallbackMethod());
    }

    /**
     * 业务逻辑处理
     *
     * @param method
     * @param args
     * @param retry
     * @param lyRpcRobinType
     * @param ipAddress
     * @param simpleName
     * @param timeOut
     * @param bean
     * @param fallbackMethod
     * @return
     */
    private Object getObject(Method method, Object[] args, int retry, LyRpcRobinType lyRpcRobinType,
                             String ipAddress, String simpleName, int timeOut, Object bean, String fallbackMethod) {
        String name = LyRpcBeanUtil.getClassName(lyRpcServer.name(), classz);
        String key2 = simpleName + "_" + name;
        LyRpcReferenceDto lyRpcReference = LyRpcData.getLyRpcReference(key2);
        try {
            // 设置请求参数
            JSONObject param = new JSONObject();
            param.put("name", LyRpcBeanUtil.getClassName(lyRpcServer.name(), classz));
            param.put("method", method.getName());
            param.put("fileName", StrUtil.EMPTY);
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof File) {
                    File file = (File) args[i];
                    param.put("fileName", file.getName());
                    args[i] = FileUtils.fileToBase64(file);
                    break;
                }
            }
            param.put("args", args);
            // 设置请求头
            Map<String, String> headers = new HashMap<>();
            if (Objects.equals(CommunicationTypeEnum.SIMPLE.getType(), lyRpcClientProperties.getType())) {
                headers.put("user", lyRpcClientProperties.getUser());
                headers.put("password", lyRpcClientProperties.getPassword());
            }
            headers.put("version", lyRpcReference.getVersion());
            LyGatewayProperties bean1 = null;
            try {
                bean1 = SpringUtil.getBean(LyGatewayProperties.class);
            } catch (NoSuchBeanDefinitionException e) {
                log.info("LyGatewayProperties bean is null");
            }
            // 启动链路追踪TLog
            if (Objects.nonNull(bean1) && Objects.nonNull(bean1.getTLog()) && bean1.getTLog().getOpen()) {
                headers.put(bean1.getTLog().getTraceId(), MDC.get(bean1.getTLog().getTraceId()));
            }

            for (int i = 0; i < retry; i++) {
                // 使用轮训负载均衡器
                LyRpcServerDto lyRpcServerDto = lyRpcRobinType.getServer(lyRpcReference.getApp(), lyRpcReference.getEnv(), ipAddress);
                if (Objects.nonNull(lyRpcServerDto)) {
                    String url = lyRpcServerDto.getServer() + "/lyRpcServer/getInfo";
                    if (Objects.equals(CommunicationTypeEnum.REGISTER.getType(), lyRpcClientProperties.getType())) {
                        headers.put("user", lyRpcServerDto.getUser());
                        headers.put("password", lyRpcServerDto.getUserPassword());
                    }

                    String body = StringUtils.EMPTY;
                    try (HttpResponse response = HttpRequest.post(url)
                            .body(JSON.toJSONString(param))
                            .addHeaders(headers)
                            .timeout(timeOut)
                            .execute()) {
                        body = response.body();
                    } catch (Exception e) {
                        log.error("ly-rpc 远程调度失败", e);
                    }
                    if (StrUtil.isNotBlank(body)) {
                        log.debug("{}:{}", method.getName(), body);
                        if (JSONUtil.isTypeJSON(body)) {
                            return JSON.parseObject(body, method.getReturnType());
                        }
                        return body;
                    }
                }
            }
        } catch (Exception e) {
            log.error("ly-rpc 调度失败", e);
        }
        // 配置了默认回调方法，走默认回调，否则返回null
        if (Objects.nonNull(bean) && StringUtils.isNotBlank(fallbackMethod)) {
            Method fallback = ReflectUtil.getMethodByName(bean.getClass(), fallbackMethod);
            if (Objects.nonNull(fallback)) {
                return ReflectUtil.invoke(bean, fallback, args);
            }
        }
        return null;
    }

}
